/******************************************************************
 * CAR THERMOMETER
 * ===============
 * This project measures the temperature inside and outside of my 
 * car and displays it on a LCD screen. The inside temperature is measured
 * using a TMP36 temperature sensor. The outside temperature is
 * measured using a waterproof DS18B20 temperature sensor on a long wire. 
 * 
 * Main Parts:
 *   - ATTiny84 microcontroller
 *   - TMP36 temp sensor
 *   - Waterproof DS18B20 temp sensor
 *   - HD44780U LCD 
 * 
 * Schematic can be found here:
 * 
 * Author: David Emole
 * Date: September 2013
 *
 ******************************************************************/

#include <LiquidCrystal.h>
#include <OneWire.h>

/******* LCD Parameters ***********/
//Pin Connections for the ATTiny84
byte lcd_rs = 0;
byte lcd_enable = 4;
byte lcd_d4 = 8;
byte lcd_d5 = 7;
byte lcd_d6 = 6;
byte lcd_d7 = 5; 
// initialize the LCD library with the numbers of the interface pins
LiquidCrystal lcd(lcd_rs, lcd_enable, lcd_d4, lcd_d5, lcd_d6, lcd_d7);
//scrolling parameters
boolean SCROLL_RIGHT = true;
boolean SCROLL_LEFT = false;


/***** INSIDE TEMPERATUTRE PARAMETERS ***********/
// Sensor: TMP36
byte insideTempPin = A7; 
int insideTemp = 0;
int previousInsideTemp = 0;
float conversionFactor = 1100/1023; //Internal 1.1V (1100mV) over 1023 steps


/******** OUTSIDE TEMPERATURE PARAMETERS ********/
// Sensor: DS18B20
byte outsideTempPin = 2;
OneWire ds(outsideTempPin); 
int previousOutsideTemp = 0;
int outsideTemp = 0;
// Parameters used to check if the outside sensor is connected 
long lastTimeOutsideTempWasRead;  // last time outside sensor was read
long currentMillis;      // current time
int sensorNotFoundTimeout = 5000; // time (ms) before announcing no sensor was found


void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  
  //set  internal reference voltage as analog ref
  analogReference(INTERNAL);
  
  // Show welcome message
  clearLCD();
  doubleScroll("CAR THERMOMETER", SCROLL_RIGHT, repeatChar('=',15) , SCROLL_LEFT); 
  delay(2000);
  doubleScroll(repeatChar(' ',16), SCROLL_LEFT, repeatChar(' ',16) , SCROLL_RIGHT);
}

void loop() {

  /****** Read Inside Temperature **********/
  if(calculateInsideTemp_Tmp36())
  {
    // update display only if a change in temperature has been observed
    if(abs(insideTemp - previousInsideTemp) > 0)
    {
      updateDisplay(insideTemp, 0, "Inside :");
      previousInsideTemp = insideTemp;
    }
  }
  else{
    displayMsg("No Inside Temp!", 0);
  }

  //delay between reading different temp sensors
  delay(50);


  /****** Read Outside Temperature **********/
  if(calculateOutsideTemp_OneWire())
  {
     // update display only if a change in temperature has been observed
    if(abs(outsideTemp - previousOutsideTemp) > 0)
    {
      updateDisplay(outsideTemp, 1, "Outside:");
      previousOutsideTemp = outsideTemp;
    }

    lastTimeOutsideTempWasRead = millis();
  }
  else{
    // if the temp sensor has been disconnected for a long time, print message
    currentMillis = millis();
    if(currentMillis - lastTimeOutsideTempWasRead >= sensorNotFoundTimeout)
    {
      //displayMsg("No Outside Temp", 1);
      clearLCDline(1);
    }
  }

  delay(1000); 
}

/**
 * Calculate the inside temperature. This uses the TMP36 sensor. 
 * The temperature is not returned but is set on a global variable. 
 */
boolean calculateInsideTemp_Tmp36()
{
  // Try reading from the sensor.
  // If nothing was read (i.e. 0), it means the sensor is not connected
  if(analogRead(insideTempPin) <= 0){
    return false;
  }

  // Get the average of 10 consecutive readings (for better accuracy)
  int reading = 0;
  for(int i = 0; i < 10 ; i++)
  {
    reading += analogRead(insideTempPin);
    delay(5);
  }
  reading = reading / 10 ;

  // convert that analog reading to voltage
  float voltage = reading * conversionFactor;

  //convert voltage to Celsius temperature (10mv per degree with 500 mV offset)
  float temperatureC = (voltage - 500) / 10 ;  

  //convert to Fahrenheit
  insideTemp = (temperatureC * 9 / 5) + 32.0;
  return true;
}

/**
 * Calculate the outside temperature. This use the OneWire sensor.
 * The temperature is also set on a global variable. 
 */
boolean calculateOutsideTemp_OneWire() {
  byte data[12];
  byte addr[8];
  
  ds.reset();

  // Search for the sensor. 
  // If not found, the sensor is not connected.
  if ( !ds.search(addr)) {
    ds.reset_search();
    return false;
  }
  if (OneWire::crc8( addr, 7) != addr[7]) {
    return false;
  }
  if (addr[0] != 0x28) { //ensure that the device ID is for DS18B20 temp sensor
    return false;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);    
  delay(500);
  ds.reset();
  ds.select(addr); 
  ds.write(0xBE); 

  // Read the data bytes
  for (byte i = 0; i < 9; i++) {     
    data[i] = ds.read();
  }
  
  //Convert the data byte array to Celsius temperature
  float tempCelsius = ( (data[1] << 8) + data[0] ) * 0.0625;

  // Convert to Fahrenheit
  outsideTemp = (tempCelsius * 9 / 5) + 32;
  return true;
}


/**
 * Update the LCD with the temperature. This facilitates displaying 
 * different temperatures on different lines. 
 * temp - the temperature (Fahrenheit expected)
 * lcdLine - the line in the LCD to display it on (1st line is 0, etc...) 
 * tempLabel - the label in front of the temperature (like "Room Temp:") 
 */
void updateDisplay(int temp, int lcdLine, char tempLabel[])
{
  clearLCDline(lcdLine);
  lcd.setCursor(0, lcdLine);
  lcd.print(tempLabel);

  //add spaces (needed to make the temperatures line up at the end of the line)
  byte tempLen = ((temp > 99 || temp < -9) ? 3 : ((temp > 9 || temp < 0) ? 2 : 1));
  byte numSpaces = 16 - strlen(tempLabel) - tempLen - 2;
  for(byte i = 0; i < numSpaces; i++){
    lcd.print(" ");
  }

  lcd.print(temp);
  lcd.print((char)223);  // print "degree character"
  lcd.print("F");
}


/**
 * Display a text on a given LCD line. 
 * Ensure that the text is 16 characters or less.
 */
void displayMsg(char msg[], byte lcdLine)
{
  clearLCDline(lcdLine);
  lcd.setCursor(0, lcdLine);
  lcd.print(msg);
}

/**
 * Clear a given line on the LCD.
 */
void clearLCDline(byte lcdLine) 
{
  lcd.setCursor(0, lcdLine);
  for(byte i = 0; i < 16; i++) 
  { 
    lcd.print(" ");
  } 
} 

/**
 * Clear the whole LCD screen
 */
void clearLCD()
{
  clearLCDline(0);
  clearLCDline(1);
}

/**
 * This is used to write out a text on the LCD screen, 
 * one character at a time with a short delay in between.
 */
void stepWrite(char text[], byte lcdLine)
{
  byte len = strlen(text);
  for(byte i = 0; i < len; i++)
  {
    lcd.setCursor(i, lcdLine);
    lcd.print(text[i]);
    delay(100);
  }
}

/**
 * This used to scroll two texts on the screen. 
 * Each text can be scrolled in a different different direction. 
 * Each text must be 16 characters or less.
 */
void doubleScroll(char text1[], boolean directionOne, char* text2, boolean directionTwo)
{
  byte lastIndex1 = strlen(text1) - 1;
  byte lastIndex2 = strlen(text2) - 1;
  
  // The specified direction determines the starting index
  byte currIndexOne = (directionOne == SCROLL_RIGHT) ? 0 : 15;
  byte currIndexTwo = (directionTwo == SCROLL_RIGHT) ? 0 : 15;
  
  /*
   * Go through every cell on the LCD and print the character for that cell.
   */
  while(currIndexOne <= 15 && currIndexOne >= 0 && currIndexTwo <= 15 && currIndexTwo >= 0)
  {
    if(currIndexOne <= lastIndex1){
      lcd.setCursor(currIndexOne, 0);
      lcd.print(text1[currIndexOne]);
    }

    if(currIndexTwo <= lastIndex2){
      lcd.setCursor(currIndexTwo, 1);
      lcd.print(text2[currIndexTwo]);
    }
    
    //Pause after writing one character on each line.
    // The times specified here determines the speed of scrolling.
    delay(100);
    
    //go to next index
    currIndexOne = (directionOne == SCROLL_RIGHT) ? (currIndexOne + 1) : (currIndexOne - 1);
    currIndexTwo = (directionTwo == SCROLL_RIGHT) ? (currIndexTwo + 1) : (currIndexTwo - 1);
  }
}


/**
 * Create a series of repeated characters given the length
 */
char* repeatChar(char c, int num){
  char* str = (char*)malloc(num + 1);
  memset (str, c, num);
  str[num] = '\0';
  return str;
}

